package br.edu.ufcg.unknown.semantico;

import java.util.LinkedList;

import br.edu.ufcg.unknown.exception.AnalisadorSemanticoUnknownRuntimeException;
import br.edu.ufcg.unknown.lexico.Token;
import br.edu.ufcg.unknown.semantico.arvore.GeradorDeArvores;
import br.edu.ufcg.unknown.semantico.arvore.Nodo;

public class AnalisadorSemantico {

	private TabelaDeSimbolos simbolos;

	private Atributo ultimoAtributo;

	public Nodo raiz;

	private LinkedList<GeradorDeArvores> geradoresDeArvores = new LinkedList<GeradorDeArvores>();

	private Nodo ultimoNodo;

	/**
	 * Linha onde se encontra o tipo sendo esperado na avaliação.
	 */
	private int linha;
	
	/**
	 * Coluna onde se encontra o tipo sendo esperado na avaliação.
	 */
	private int coluna;
	
	public AnalisadorSemantico() {
		this.simbolos = new TabelaDeSimbolos();
		this.ultimoAtributo = null;
		this.raiz = null;
	}

	public void addSimbolo(Token tk, Tipo tipo) {
		this.simbolos.addSimbolo(tk, tipo);
	}

	public Atributo getAtributo(Token tk) {
		Atributo r = this.simbolos.getAtributo(tk);
		if(r != null){
			r = r.clone();
			if(r.getTipo() != null && r.getTipo().getToken() != null){
				Token tkAtributo = r.getTipo().getToken();
				tkAtributo.setLinha(tk.getLinha());
				tkAtributo.setColuna(tk.getColuna());
			}
		}
		return r;

	}

	public void setAtributo(Token tk, int linha, int coluna) {

		if (tk == null) {
			return;
		}

		this.ultimoAtributo = this.simbolos.getAtributo(tk);

		if (this.ultimoAtributo == null) {
			this.ultimoAtributo = new Atributo(tk.getLexema(), new Tipo(tk));
		}
		
		this.linha = linha;
		this.coluna = coluna;
	}

	public void verificaCompatibilidade() {
		verificaCompatibilidade(false);
	}

	/**
	 * @param mostrarLexemaDoAtributoEsperado
	 *            True se, em casos de erro, deve mostrar também o lexema do Token cujo tipo estava
	 *            sendo esperado. False caso só deva mostrar o tipo esperado. Essa chamada será com
	 *            <i>true</i> nos casos de atribuição, onde sei exatamente o tipo esperado pela
	 *            variável e o lexema da variável, em outros casos, como avaliação de expressão, eu
	 *            não tenho esse "lexema da variável", portando só mostro o tipo esperado.
	 */
	public void verificaCompatibilidade(boolean mostrarLexemaDoAtributoEsperado) {

		if (this.ultimoNodo == null) {
			// TODO lancar erro
		}

		if (this.ultimoAtributo == null) {
			// TODO lancar erro
		}

		if (this.ultimoNodo == null || this.ultimoAtributo == null) {
			return;
		}

		if (!this.ultimoNodo.getTipo().equals(this.ultimoAtributo.getTipo())) {
			if (mostrarLexemaDoAtributoEsperado) {
				throw new AnalisadorSemanticoUnknownRuntimeException(this.ultimoAtributo,
						this.ultimoNodo.getTipo(), this.linha, this.coluna);
			} else {
				throw new AnalisadorSemanticoUnknownRuntimeException(this.ultimoAtributo.getTipo(),
						this.ultimoNodo.getTipo(), this.linha, this.coluna);
			}
		}

	}

	public boolean existeAtributo(Token identificador) {
		return this.simbolos.getAtributo(identificador) != null;
	}

	public void novoGeradorDeArvore() {
		this.geradoresDeArvores.add(new GeradorDeArvores());
	}

	public void addNodo(Nodo nodo) {
		if (this.geradoresDeArvores.size() != 0) {
			GeradorDeArvores geradorDeArvores = this.geradoresDeArvores.getLast();
			geradorDeArvores.addNodo(nodo);
		}
	}

	public Nodo resolverUltimoGeradorDeArvores() {
		if (this.geradoresDeArvores.size() != 0) {
			GeradorDeArvores geradorDeArvores = this.geradoresDeArvores.removeLast();
			Nodo nodo = geradorDeArvores.resolver();// XXX Aqui tá retornando null
			if (this.geradoresDeArvores.size() != 0) {
				this.geradoresDeArvores.getLast().addNodo(nodo);
			}
			this.ultimoNodo = nodo;
			return nodo;
		}
		return null;
	}

	public void addNodo(Token tk) {
		addNodo(new Nodo(tk));
	}

}
